﻿using System;
using System.Collections.Generic;
using System.Data.SqlClient;
using System.IO;

namespace ErrorDumper.PropertyBinding {
    public static class ExceptionPropertyBinders {

        public static readonly HashSet<IExceptionPropertyBinder>
            _globalBinders = new HashSet<IExceptionPropertyBinder>();

        public static readonly Dictionary<Type, HashSet<IExceptionPropertyBinder>>
            _typeBinders = new Dictionary<Type, HashSet<IExceptionPropertyBinder>>();

        static ExceptionPropertyBinders() {
            RegisterTypeBinder<FileNotFoundException>(new FileNotFoundExceptionPropertyBinder());
            RegisterTypeBinder<SqlException>(new SqlExceptionPropertyBinder());
        }

        public static bool RegisterGlobalBinder(IExceptionPropertyBinder binder) {
            if (binder == null)
                throw new ArgumentNullException("binder");
            return _globalBinders.Add(binder);
        }

        public static bool UnregisterGlobalBinder(IExceptionPropertyBinder binder) {
            if (binder == null)
                throw new ArgumentNullException("binder");
            return _globalBinders.Remove(binder);
        }

        public static int UnregisterGlobalBinder(Type binderType) {
            if (binderType == null)
                throw new ArgumentNullException("binderType");
            if (!typeof(IExceptionPropertyBinder).IsAssignableFrom(binderType))
                throw new ArgumentOutOfRangeException("binderType");
            return _globalBinders.RemoveWhere(binderType.IsInstanceOfType);
        }

        public static int UnregisterGlobalFilter<TBinder>()
            where TBinder : class, IExceptionPropertyBinder {
            return _globalBinders.RemoveWhere(i => i is TBinder);
        }

        public static int UnregisterGlobalBinder(Predicate<IExceptionPropertyBinder> predicate) {
            if (predicate == null)
                throw new ArgumentNullException("predicate");
            return _globalBinders.RemoveWhere(predicate);
        }

        public static bool RegisterTypeBinder(Type type, IExceptionPropertyBinder binder) {
            if (type == null)
                throw new ArgumentNullException("type");
            if (binder == null)
                throw new ArgumentNullException("binder");

            HashSet<IExceptionPropertyBinder> filters;
            if (!_typeBinders.TryGetValue(type, out filters))
                _typeBinders.Add(type, filters = new HashSet<IExceptionPropertyBinder>());
            return filters.Add(binder);
        }

        public static bool RegisterTypeBinder<T>(IExceptionPropertyBinder binder) {
            return RegisterTypeBinder(typeof(T), binder);
        }

        public static ExceptionPropertyCollection BindProperties(
            ExceptionPropertyCollection properties, object instance) {

            if (instance == null)
                throw new ArgumentNullException("instance");

            foreach (var binder in _globalBinders)
                properties = binder.BindProperties(properties, false);

            foreach (var entry in _typeBinders)
                if (entry.Key.IsInstanceOfType(instance))
                    foreach (var binder in entry.Value)
                        properties = binder.BindProperties(properties, false);

            return properties;
        }
    }
}